/**
 * 20.3.19：版本升级更新
 * 21.5.20：ios不支持WebGL2，在编译前增加检测，非WebGL2时修改语法
 * 20220304:[require] to [import]...jordia
 */
import {ShaderMaterial,Material,ShaderLib,UniformsUtils,Color,ShaderChunk} from 'three';

/**
 * 边缘突出材质
 * @params values {object} physicalMaterial所支持的属性及此材质特有的属性
 * smoothStart:0.5,平滑起始值（越大边缘越薄，0-1）
 * rimColor:new Color(1,1,1),边缘颜色
 * rimGamma:1,边缘显示伽马值（大于0）
 * hollow:true,是否镂空（transparent为true有效）
 * fillFactor:1,整体透明度（大于0）
 * fillColor:new Color(0,0.5,0.5),透明区域颜色
 */
export default class RimMaterial extends ShaderMaterial{
    constructor(values={}){
        super();
        this._values=values;
        this.init();
    }

    //------------------------------------------对外属性及方法-----------------------------------------

    /**
     * 属性赋值
     * @param values {object} physicalMaterial所支持的属性及此材质特有的属性
     */
    set values(values){
        for(let key in values){
            if(this.hasOwnProperty(''+key+'')){
                if(values[''+key+'']!==undefined){
                    this[''+key+'']=values[''+key+''];
                }
            }else{
                console.warn(''+key+' is not a property of RimMaterial!');
            }
        }
        this.needsUpdate=true;
    }

    /**
     * 获取uniform组
     * @returns {*}
     */
    get values(){
        return this._paras;
    }

    /**
     * 拷贝
     * @param source
     * @returns {RimMaterial}
     */
    copy(source){
        Material.prototype.copy.call( this, source );
        this.values=source.values;
        return this;
    }

    /**
     * 克隆
     * @returns {RimMaterial}
     */
    clone() {
        let c=new RimMaterial();
        c.copy(this);
        return c;
    }

    //------------------------------------------私有属性及方法-------------------------------------------
    onBeforeCompile(shader, renderer) {
        if(!shader['isWebGL2']){
            let pf='#extension GL_OES_standard_derivatives : enable';
            shader.fragmentShader=pf+'\n'+fragmentShader;
        }
    }
    init(){
        this.initVariable();
        this.initGetter();
        this.initSetter();
        this.initProperty();
        this.initShaders();
        this.initUniforms();
        this.initialize();
    }
    initVariable(){
        this._paras={};
        this._exclusive={
            smoothStart:{value:0.5},
            rimColor:{value:new Color(1,1,1) },
            rimGamma:{value:1},
            hollow:{value:true},
            fillFactor:{value:1},
            fillColor:{value:new Color(0,0.5,0.5)},
        };
        this.getter={};
        this.setter={};
    }
    initSetter() {
        this.setter.default = (key) => {
            return () => {
                if (this.uniforms.hasOwnProperty(key)) {
                    return this.uniforms[key].value;
                }
                return this._paras[key];
            };
        };
    }
    initGetter(){
        this.getter.default=(key)=>{
            return (v) => {
                if (this.uniforms.hasOwnProperty(key)) {
                    this.uniforms[key].value = v;
                }
                this._paras[key] = v;
            };
        };
    }
    getGetter(key) {
        if(this.getter.hasOwnProperty(key)){
            return this.getter[key](key);
        }else{
            return this.getter.default(key);
        }
    }
    getSetter(key){
        if(this.setter.hasOwnProperty(key)){
            return this.setter[key](key);
        }else{
            return this.setter.default(key);
        }
    }
    definePropertyByObject(obj){
        for(let key of Reflect.ownKeys(obj)){
            if(key!=='constructor'&&key!=="prototype"&&key!=="name"&&key!=='id'&&key!=='uuid'){
                let ds=Object.getOwnPropertyDescriptor(obj,key);
                this._paras[key]=ds.value.value;
                let fc={
                    enumerable: true,
                    configurable: true,
                    set:this.getGetter(key),
                    get:this.getSetter(key)
                };
                Object.defineProperty(this,key,fc);
            }
        }
    }
    initProperty(){
        this.definePropertyByObject(ShaderLib[ 'physical' ].uniforms);
        this.definePropertyByObject(this._exclusive);
        this.type='RimMaterial';
        this.lights=true;
        this.clipping=true;
    }
    initShaders(){
        this.vertexShader=vertexShader;
        this.fragmentShader=fragmentShader;
    }
    initUniforms(){
        this.uniforms=UniformsUtils.merge( [ShaderLib[ 'physical' ].uniforms, this._exclusive] );
        this._exclusive=null;
    }
    initialize(){
        this.values=this._values;
        this._values=null;
    }
}

const vertexShader=[
    "#define STANDARD" ,
    "varying vec3 vViewPosition;" ,
    "#ifndef FLAT_SHADED" ,
    "varying vec3 vNormal;" ,
    "#ifdef USE_TANGENT" ,
    "varying vec3 vTangent;" ,
    "varying vec3 vBitangent;" ,
    "#endif" ,
    "#endif" ,
    "#include <common>" ,
    "#include <uv_pars_vertex>" ,
    "#include <uv2_pars_vertex>" ,
    "#include <displacementmap_pars_vertex>" ,
    "#include <color_pars_vertex>" ,
    "#include <fog_pars_vertex>" ,
    "#include <morphtarget_pars_vertex>" ,
    "#include <skinning_pars_vertex>" ,
    "#include <shadowmap_pars_vertex>" ,
    "#include <logdepthbuf_pars_vertex>" ,
    "#include <clipping_planes_pars_vertex>" ,

    "varying vec3 world_pos;",
    "varying vec3 world_normal;",

    "void main() {" ,
    "#include <uv_vertex>" ,
    "#include <uv2_vertex>" ,
    "#include <color_vertex>" ,
    "#include <beginnormal_vertex>" ,
    "#include <morphnormal_vertex>" ,
    "#include <skinbase_vertex>" ,
    "#include <skinnormal_vertex>" ,
    "#include <defaultnormal_vertex>" ,
    "#ifndef FLAT_SHADED" ,
    "vNormal = normalize( transformedNormal );" ,
    "#ifdef USE_TANGENT" ,
    "vTangent = normalize( transformedTangent );" ,
    "vBitangent = normalize( cross( vNormal, vTangent ) * tangent.w );" ,
    "#endif" ,
    "#endif" ,
    "#include <begin_vertex>" ,
    "#include <morphtarget_vertex>" ,
    "#include <skinning_vertex>" ,
    "#include <displacementmap_vertex>" ,
    "#include <project_vertex>" ,
    "#include <logdepthbuf_vertex>" ,
    "#include <clipping_planes_vertex>" ,
    "vViewPosition = - mvPosition.xyz;" ,
    "#include <worldpos_vertex>" ,
    "#include <shadowmap_vertex>" ,
    "#include <fog_vertex>" ,

    "world_pos=(modelMatrix*vec4(position,1.)).xyz;",
    "world_normal=normalize(mat3(modelMatrix) * normal);",

    "}"
].join('\n');

const fragmentShader=[
    "#define STANDARD" ,
    "#ifdef PHYSICAL" ,
    "#define REFLECTIVITY" ,
    "#define CLEARCOAT" ,
    "#define TRANSPARENCY" ,
    "#endif" ,
    "uniform vec3 diffuse;" ,
    "uniform vec3 emissive;" ,
    "uniform float roughness;" ,
    "uniform float metalness;" ,
    "uniform float opacity;" ,
    "#ifdef TRANSPARENCY" ,
    "uniform float transparency;" ,
    "#endif" ,
    "#ifdef REFLECTIVITY" ,
    "uniform float reflectivity;" ,
    "#endif" ,
    "#ifdef CLEARCOAT" ,
    "uniform float clearcoat;" ,
    "uniform float clearcoatRoughness;" ,
    "#endif" ,
    "#ifdef USE_SHEEN" ,
    "uniform vec3 sheen;" ,
    "#endif" ,
    "varying vec3 vViewPosition;" ,
    "#ifndef FLAT_SHADED" ,
    "varying vec3 vNormal;" ,
    "#ifdef USE_TANGENT" ,
    "varying vec3 vTangent;" ,
    "varying vec3 vBitangent;" ,
    "#endif" ,
    "#endif" ,
    "#include <common>" ,
    "#include <packing>" ,
    "#include <dithering_pars_fragment>" ,
    "#include <color_pars_fragment>" ,
    "#include <uv_pars_fragment>" ,
    "#include <uv2_pars_fragment>" ,
    "#include <map_pars_fragment>" ,
    "#include <alphamap_pars_fragment>" ,
    "#include <aomap_pars_fragment>" ,
    "#include <lightmap_pars_fragment>" ,
    "#include <emissivemap_pars_fragment>" ,
    "#include <bsdfs>" ,
    "#include <cube_uv_reflection_fragment>" ,
    "#include <envmap_common_pars_fragment>" ,
    "#include <envmap_physical_pars_fragment>" ,
    "#include <fog_pars_fragment>" ,
    "#include <lights_pars_begin>" ,
    "#include <lights_physical_pars_fragment>" ,
    "#include <shadowmap_pars_fragment>" ,
    "#include <bumpmap_pars_fragment>" ,
    "#include <normalmap_pars_fragment>" ,
    ShaderChunk.clearcoat_pars_fragment,
    "#include <roughnessmap_pars_fragment>" ,
    "#include <metalnessmap_pars_fragment>" ,
    "#include <logdepthbuf_pars_fragment>" ,
    "#include <clipping_planes_pars_fragment>" ,

    "uniform vec3 rimColor;",
    "uniform vec3 fillColor;",
    "uniform float rimGamma;",
    "uniform bool hollow;",
    "uniform float fillFactor;",
    "uniform float smoothStart;",
    "varying vec3 world_pos;",
    "varying vec3 world_normal;",

    "void main() {"  ,
    "#include <clipping_planes_fragment>" ,
    "vec4 diffuseColor = vec4( diffuse, opacity );" ,
    "ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );" ,
    "vec3 totalEmissiveRadiance = emissive;" ,
    "#include <logdepthbuf_fragment>" ,
    "#include <map_fragment>" ,
    "#include <color_fragment>" ,
    "#include <alphamap_fragment>" ,
    "#include <alphatest_fragment>" ,
    "#include <roughnessmap_fragment>" ,
    "#include <metalnessmap_fragment>" ,
    "#include <normal_fragment_begin>" ,
    "#include <normal_fragment_maps>" ,
    "#include <clearcoat_normal_fragment_begin>" ,
    "#include <clearcoat_normal_fragment_maps>" ,
    "#include <emissivemap_fragment>" ,
    "#include <lights_physical_fragment>" ,
    "#include <lights_fragment_begin>" ,
    "#include <lights_fragment_maps>" ,
    "#include <lights_fragment_end>" ,
    "#include <aomap_fragment>" ,
    "vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;" ,
    "#ifdef TRANSPARENCY" ,
    "diffuseColor.a *= saturate( 1. - transparency + linearToRelativeLuminance( reflectedLight.directSpecular + reflectedLight.indirectSpecular ) );" ,
    "#endif" ,

    "   float rimG=abs(rimGamma);" ,
    "   float fillF=abs(fillFactor);" ,
    "   vec3 viewDir = normalize( cameraPosition - world_pos);",
    "   float rim = clamp(1. - max(dot(viewDir, world_normal), 0.0),0.,1.);",
    "   rim = smoothstep(clamp(smoothStart,0.,1.), 1.0, rim);",
    "   vec3 finalRim = rimColor * vec3(rim, rim, rim);",
    "   vec3 finalColor = finalRim + diffuseColor.xyz;",
    "   vec3 finalColorGamma = vec3(pow(finalColor.r, rimG),pow(finalColor.g, rimG),pow(finalColor.b, rimG));",
    "   if(hollow){",
    "       vec3 fCol=mix(fillColor,outgoingLight,rim);" ,
    "       gl_FragColor = vec4( fCol*finalColorGamma, rim*fillF);" ,
    "   }else{" ,
    "       vec3 fCol=mix(fillColor,outgoingLight,rim);" ,
    "       gl_FragColor = vec4( fCol*finalColorGamma, (diffuseColor.a+rim)*fillF);" ,
    "   }" ,

    "#include <tonemapping_fragment>" ,
    "#include <encodings_fragment>" ,
    "#include <fog_fragment>" ,
    "#include <premultiplied_alpha_fragment>" ,
    "#include <dithering_fragment>" ,
    "}"
].join('\n');